package com.ikingtech.platform.service.application.service.generater;

import com.ikingtech.framework.sdk.application.model.code.ApplicationFieldParam;
import com.ikingtech.framework.sdk.application.model.code.ApplicationModelCodeGenParam;
import com.ikingtech.framework.sdk.context.security.Me;
import com.ikingtech.framework.sdk.enums.common.ConditionComparatorEnum;
import com.ikingtech.framework.sdk.enums.common.JavaFieldTypeEnum;
import com.ikingtech.framework.sdk.enums.common.TableFieldTypeEnum;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.platform.service.application.entity.ApplicationModelDO;
import com.ikingtech.platform.service.application.entity.ApplicationModelFieldDO;
import com.ikingtech.platform.service.application.entity.ApplicationModelRelationDO;
import com.ikingtech.platform.service.application.service.ApplicationModelFieldService;
import com.ikingtech.platform.service.application.service.ApplicationModelRelationService;
import com.ikingtech.platform.service.application.service.ApplicationModelService;
import lombok.RequiredArgsConstructor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;

/**
 * @author tie yan
 */
@RequiredArgsConstructor
public abstract class AbstractCodeGenerator implements ApplicationCodeGenerator {

    private final ApplicationModelService modelService;

    private final ApplicationModelFieldService fieldService;

    private final ApplicationModelRelationService modelRelationService;

    protected Boolean relateToApprove() {
        return false;
    }

    protected String initiateFormModelId() {
        return Tools.Str.EMPTY;
    }

    protected String approveBusinessType() {
        return Tools.Str.EMPTY;
    }

    @Override
    public Map<String, String> generate(String basePath, String packageName, List<ApplicationModelDO> entities) {
        Map<String, String> result = new HashMap<>();
        List<String> modelIds = Tools.Coll.convertList(entities, ApplicationModelDO::getId);
        List<ApplicationModelRelationDO> modelRelationEntities = this.modelRelationService.listByMasterModelIds(modelIds);
        modelIds.addAll(Tools.Coll.convertList(modelRelationEntities, ApplicationModelRelationDO::getModelId));

        Map<String, List<ApplicationModelRelationDO>> modelRelationMap = Tools.Coll.convertGroup(modelRelationEntities, ApplicationModelRelationDO::getMasterModelId);

        Map<String, ApplicationModelDO> modelMap = Tools.Coll.convertMap(entities, ApplicationModelDO::getId);
        if (Tools.Coll.isNotBlank(modelRelationEntities)) {
            modelMap.putAll(Tools.Coll.convertMap(this.modelService.listByIds(Tools.Coll.convertList(modelRelationEntities, ApplicationModelRelationDO::getModelId)), ApplicationModelDO::getId));
        }
        List<ApplicationModelFieldDO> fieldEntities = this.fieldService.listByModelIds(modelIds);
        Map<String, ApplicationModelFieldDO> fieldMap = Tools.Coll.convertMap(fieldEntities, ApplicationModelFieldDO::getId);
        Map<String, List<ApplicationModelFieldDO>> modelFieldMap = Tools.Coll.convertGroup(fieldEntities, ApplicationModelFieldDO::getModelId);

        for (ApplicationModelDO entity : entities) {
            List<ApplicationModelFieldDO> currentModelFieldEntities = modelFieldMap.get(entity.getId());
            String modelPackageName = packageName + "." + Tools.Str.toDotSeparate(entity.getCode());
            ApplicationModelCodeGenParam codeGenParam = this.createCodeGenParam(modelPackageName, entity, currentModelFieldEntities);
            codeGenParam.setGroupId(packageName);
            codeGenParam.setArtifactId(Tools.Str.toHyphenSeparate(entity.getCode()));
            codeGenParam.setQueryFields(Tools.Coll.convertList(currentModelFieldEntities, fieldEntity -> Boolean.TRUE.equals(fieldEntity.getQueryField()), this::createQueryField));
            if (Boolean.TRUE.equals(this.relateToApprove()) && entity.getId().equals(this.initiateFormModelId())) {
                codeGenParam.setRelateToApprove(true);
                codeGenParam.setApproveBusinessType(this.approveBusinessType());
            }
            List<ApplicationModelRelationDO> currentModelRelationEntities = modelRelationMap.get(entity.getId());

            String rootPath = basePath + "/" + entity.getCode();
            String srcPath = rootPath + "/src/main";
            String packagePath = srcPath + "/java/" + codeGenParam.getPackageName().replace(".", "/");
            if (Tools.Coll.isNotBlank(currentModelRelationEntities)) {
                for (ApplicationModelRelationDO modelRelationEntity : currentModelRelationEntities) {
                    List<ApplicationModelFieldDO> slaveModelFieldEntities = modelFieldMap.get(modelRelationEntity.getModelId());
                    ApplicationModelDO slaveModelEntity = modelMap.get(modelRelationEntity.getModelId());

                    ApplicationModelCodeGenParam slaveModelCodeGenParam = this.createCodeGenParam(modelPackageName, slaveModelEntity, slaveModelFieldEntities);
                    slaveModelCodeGenParam.setMasterModelCode(entity.getCode());
                    slaveModelCodeGenParam.setUpperCamelMasterModelCode(Tools.Str.toUpperCamelCaseWithoutSeparate(slaveModelCodeGenParam.getMasterModelCode()));
                    slaveModelCodeGenParam.setSlaveModel(true);
                    slaveModelCodeGenParam.setQueryFields(Tools.Coll.convertList(slaveModelFieldEntities,
                            slaveModelFieldEntity -> Boolean.TRUE.equals(slaveModelFieldEntity.getQueryField()),
                            slaveModelFieldEntity -> this.createQueryField(slaveModelFieldEntity.getName(), Tools.Str.format("{}{}", slaveModelEntity.getCode(), Tools.Str.toUpperCamelCaseWithoutSeparate(slaveModelFieldEntity.getName())), JavaFieldTypeEnum.valueOf(slaveModelFieldEntity.getType()), ConditionComparatorEnum.valueOf(slaveModelFieldEntity.getCompareType()), slaveModelFieldEntity.getRemark())));
                    slaveModelCodeGenParam.setQueryExecuteCondition(Tools.Str.join(Tools.Coll.convertList(slaveModelCodeGenParam.getQueryFields(), ApplicationFieldParam::getCondition), "||\n\t\t\t\t"));
                    slaveModelCodeGenParam.setForeignKeyFieldName(fieldMap.get(modelRelationEntity.getForeignKeyFieldId()).getName());
                    slaveModelCodeGenParam.setUpperCamelForeignKeyFieldName(Tools.Str.toUpperCamelCaseWithoutSeparate(slaveModelCodeGenParam.getForeignKeyFieldName()));

                    ApplicationFieldParam slaveFieldParam = this.createModelField(slaveModelEntity.getCode() + "List", JavaFieldTypeEnum.LIST, JavaFieldTypeEnum.MODEL, slaveModelEntity.getRemark(), slaveModelEntity.getCode());
                    slaveFieldParam.setRelateModel(slaveModelCodeGenParam);
                    codeGenParam.getSlaveFields().add(slaveFieldParam);
                    Tools.Velocity velocity = Tools.Velocity.instance().context("param", slaveModelCodeGenParam);
                    result.put(packagePath + "/service/" + slaveModelCodeGenParam.getUpperCamelModelCode() + "Service.java", velocity.generate("template/Service.java.vm"));
                    result.put(packagePath + "/model/" + slaveModelCodeGenParam.getUpperCamelModelCode() + "DTO.java", velocity.generate("template/Model.java.vm"));
                    result.put(packagePath + "/entity/" + slaveModelCodeGenParam.getUpperCamelModelCode() + "DO.java", velocity.generate("template/Entity.java.vm"));
                    result.put(packagePath + "/mapper/" + slaveModelCodeGenParam.getUpperCamelModelCode() + "Mapper.java", velocity.generate("template/Mapper.java.vm"));
                }
            }
            Tools.Velocity velocity = Tools.Velocity.instance().context("param", codeGenParam);
            result.put(packagePath + "/controller/" + codeGenParam.getUpperCamelModelCode() + "Controller.java", velocity.generate("template/Controller.java.vm"));
            result.put(packagePath + "/service/" + codeGenParam.getUpperCamelModelCode() + "Service.java", velocity.generate("template/Service.java.vm"));
            result.put(packagePath + "/model/" + codeGenParam.getUpperCamelModelCode() + "DTO.java", velocity.generate("template/Model.java.vm"));
            result.put(packagePath + "/model/" + codeGenParam.getUpperCamelModelCode() + "QueryParamDTO.java", velocity.generate("template/QueryParam.java.vm"));
            result.put(packagePath + "/entity/" + codeGenParam.getUpperCamelModelCode() + "DO.java", velocity.generate("template/Entity.java.vm"));
            result.put(packagePath + "/mapper/" + codeGenParam.getUpperCamelModelCode() + "Mapper.java", velocity.generate("template/Mapper.java.vm"));
            result.put(rootPath + "/pom.xml", velocity.generate("template/ModulePom.xml.vm"));
        }
        return result;
    }

    private ApplicationModelCodeGenParam createCodeGenParam(String packageName, ApplicationModelDO entity, List<ApplicationModelFieldDO> fieldEntities) {
        ApplicationModelCodeGenParam codeGenParam = new ApplicationModelCodeGenParam();
        codeGenParam.setPackageName(packageName);
        codeGenParam.setModelName(entity.getName());
        codeGenParam.setModelCode(entity.getCode());
        codeGenParam.setHyphenModelCode(Tools.Str.toHyphenSeparate(entity.getCode()));
        codeGenParam.setUpperCamelModelCode(Tools.Str.toUpperCamelCaseWithoutSeparate(entity.getCode()));
        codeGenParam.setSnakeCaseModelCode(Tools.Str.toHyphenSeparate(entity.getCode()));
        codeGenParam.setAuthor(Me.name());
        codeGenParam.setDbTableSerialVersionUid(String.valueOf(ThreadLocalRandom.current().nextLong()));
        codeGenParam.setModelSerialVersionUid(String.valueOf(ThreadLocalRandom.current().nextLong()));
        codeGenParam.setQueryParamSerialVersionUid(String.valueOf(ThreadLocalRandom.current().nextLong()));

        codeGenParam.setDbTableFields(Tools.Coll.convertList(fieldEntities, fieldEntity -> Boolean.TRUE.equals(fieldEntity.getDbTableField()), this::createDbField));
        codeGenParam.setModelFields(Tools.Coll.convertList(fieldEntities, this::createModelField));
        codeGenParam.setQueryFields(new ArrayList<>());
        codeGenParam.setSlaveFields(new ArrayList<>());
        return codeGenParam;
    }

    private ApplicationFieldParam createModelField(ApplicationModelFieldDO fieldEntity) {
        return this.createModelField(fieldEntity.getName(), JavaFieldTypeEnum.valueOf(fieldEntity.getType()), Tools.Str.isBlank(fieldEntity.getGenericType()) ? null : JavaFieldTypeEnum.valueOf(fieldEntity.getGenericType()), fieldEntity.getRemark(), Tools.Str.EMPTY);
    }

    private ApplicationFieldParam createModelField(String fieldName, JavaFieldTypeEnum type, JavaFieldTypeEnum genericType, String remark, String slaveModelCode) {
        ApplicationFieldParam fieldParam = new ApplicationFieldParam();
        fieldParam.setName(fieldName);
        fieldParam.setUpperCamelName(Tools.Str.toUpperCamelCaseWithoutSeparate(fieldParam.getName()));
        if (JavaFieldTypeEnum.LIST.equals(type)) {
            if (JavaFieldTypeEnum.MODEL.equals(genericType)) {
                fieldParam.setModifier(Tools.Str.format(type.modifier, Tools.Str.toUpperCamelCaseWithoutSeparate(slaveModelCode) + "DTO"));
            } else {
                fieldParam.setModifier(Tools.Str.format(type.modifier, genericType.modifier));
            }
        } else {
            fieldParam.setModifier(type.modifier);
        }
        fieldParam.setRemark(remark);
        return fieldParam;
    }

    private ApplicationFieldParam createDbField(ApplicationModelFieldDO fieldEntity) {
        ApplicationFieldParam fieldParam = new ApplicationFieldParam();
        fieldParam.setName(fieldEntity.getName());
        fieldParam.setUpperCamelName(Tools.Str.toUpperCamelCaseWithoutSeparate(fieldEntity.getName()));
        fieldParam.setSnakeCaseName(Tools.Str.toSnakeCase(fieldEntity.getName()));
        fieldParam.setModifier(TableFieldTypeEnum.valueOf(fieldEntity.getDbTableFieldType()).javaType.modifier);
        return fieldParam;
    }

    private ApplicationFieldParam createQueryField(ApplicationModelFieldDO fieldEntity) {
        return this.createQueryField(fieldEntity.getName(), fieldEntity.getName(), JavaFieldTypeEnum.valueOf(fieldEntity.getType()), ConditionComparatorEnum.valueOf(fieldEntity.getCompareType()), fieldEntity.getRemark());
    }

    private ApplicationFieldParam createQueryField(String name, String queryName, JavaFieldTypeEnum type, ConditionComparatorEnum compareType, String remark) {
        ApplicationFieldParam fieldParam = new ApplicationFieldParam();
        fieldParam.setName(name);
        fieldParam.setUpperCamelName(Tools.Str.toUpperCamelCaseWithoutSeparate(fieldParam.getName()));
        fieldParam.setQueryName(queryName);
        fieldParam.setUpperCamelQueryName(Tools.Str.toUpperCamelCaseWithoutSeparate(fieldParam.getQueryName()));
        fieldParam.setModifier(type.modifier);
        fieldParam.setRemark(remark);
        fieldParam.setCompareType(compareType.expression);
        fieldParam.setCondition(Tools.Str.format(type.checkNull, "queryParam.get" + fieldParam.getUpperCamelQueryName() + "()"));
        return fieldParam;
    }
}
